import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MissionEntity } from './mission.entity';
import { Repository } from 'typeorm';
import { ErrorEnum } from '~/constants/error-code.constant';
import { isNil } from 'lodash';
import { CreateMissionDto, MissionRecordDto, SearchMissionDto } from './mission.dto';
import { CommonStatus } from '~/common/entity/common.entity';
import { MissionUserEntity } from './mission-user.entity';
import { MissionLogService } from './mission-log/mission-log.service';
import { PointsService } from '../points/points.service';
import { CountMissionModel, CountMissionModelWithNormal, MissionType } from './mission.model';
import { CatalogService } from '../catalog/catalog.service';
import dayjs from 'dayjs';
import { standardPaginate, StandardPagination } from '~/helper/paginate';

@Injectable()
export class MissionService {
  private readonly logger = new Logger(MissionService.name);

  constructor(
    @InjectRepository(MissionEntity)
    private readonly missionRepo: Repository<MissionEntity>,
    @InjectRepository(MissionUserEntity)
    private readonly missionUserRepo: Repository<MissionUserEntity>,
    private readonly missionLogService: MissionLogService,
    private readonly pointsService: PointsService,
    private readonly catalogService: CatalogService
  ) {}

  /**
   * 创建任务
   * @description 创建一个新的任务
   * @param mission 任务创建数据传输对象
   * @returns Promise<MissionEntity>
   * @throws BadRequestException 当任务名称重复时抛出异常
   */
  async create(mission: CreateMissionDto): Promise<MissionEntity> {
    // 检查任务名称是否重复
    const existingMission = await this.missionRepo.findOne({
      where: {
        name: mission.name,
        isDeleted: CommonStatus.NO,
      },
    });

    if (existingMission) {
      throw new BadRequestException('任务名称已存在');
    }

    return await this.missionRepo.save({
      ...mission,
      pointsRule: { id: mission.pointsRuleId },
    });
  }

  async findAll(query: SearchMissionDto): Promise<StandardPagination<MissionEntity>> {
    const queryBuilder = this.missionRepo
      .createQueryBuilder('mission')
      .leftJoinAndSelect('mission.pointsRule', 'pointsRule')
      .where('mission.isDeleted = :isDeleted', { isDeleted: CommonStatus.NO });

    // 添加名称模糊搜索
    if (query.name) {
      queryBuilder.andWhere('mission.name LIKE :name', { name: `%${query.name}%` });
    }

    // 添加任务类型搜索
    if (query.type) {
      queryBuilder.andWhere('mission.missionType = :type', { type: query.type });
    }

    // 添加排序
    queryBuilder.orderBy('mission.createdAt', query.order || 'DESC');

    return await standardPaginate(queryBuilder, {
      page: query.page,
      pageSize: query.pageSize,
    });
  }

  async getMissionByCatalog(
    mission: MissionEntity,
    uid?: number
  ): Promise<CountMissionModelWithNormal> {
    const catalogCategoryId = Number(mission.missionRelatedData);

    const catalogs = await this.catalogService.findActiveLeafNodes(catalogCategoryId);

    if (isNil(uid)) {
      return {
        targetCount: catalogs.length,
        completedCount: 0,
        completedPercent: 0,
      };
    }

    // 查询这些任务是否完成
    const finishArticleIds: number[] = await this.missionLogService.findByCatalog(
      catalogCategoryId,
      uid
    );

    const finishCatalogs = catalogs.filter((catalog) =>
      finishArticleIds.includes(catalog.articleId)
    );

    return {
      targetCount: catalogs.length,
      completedCount: finishCatalogs.length,
      completedPercent: parseFloat((finishCatalogs.length / catalogs.length).toFixed(2)),
    } as CountMissionModel;
  }

  async getMissionByNormal(
    mission: MissionEntity,
    uid?: number
  ): Promise<Partial<CountMissionModelWithNormal>> {
    if (isNil(uid)) {
      return {
        targetCount: Number(mission?.pointsRule?.targetCount ?? 0),
        completedCount: 0,
        completedPercent: parseFloat((0 / (mission?.pointsRule?.targetCount ?? 0)).toFixed(2)),
      };
    }

    const missionLogs: MissionUserEntity[] = await this.missionUserRepo.find({
      where: {
        mission: { id: mission.id },
        user: { id: uid },
      },
      relations: ['mission'],
    });

    // 进行映射，不存在记录则认为完成数为 0
    return {
      targetCount: Number(mission.pointsRule?.targetCount ?? 0),
      completedCount: Number(missionLogs.length ?? 0),
      completedPercent: parseFloat(
        ((missionLogs.length ?? 0) / (mission?.pointsRule?.targetCount ?? 0)).toFixed(2)
      ),
    };
  }

  async findAllByTenantId(uid?: number): Promise<CountMissionModel[]> {
    const missions = await this.missionRepo.find({
      select: ['id', 'name', 'description', 'missionType', 'missionRelatedData'],
      where: {
        // tenant: { id: tenantId }, 后期添加
        isDeleted: CommonStatus.NO as any,
      },
      relations: ['pointsRule'],
      order: { createdAt: 'DESC' },
    });

    // 根据用户的uid和任务id，获取用户的任务进度
    return (await Promise.all(
      missions.map(
        async (
          mission: MissionEntity
        ): Promise<Partial<MissionEntity> & CountMissionModelWithNormal> => {
          switch (mission.missionType) {
            case MissionType.CATALOG: {
              const { targetCount, completedCount, completedPercent } =
                await this.getMissionByCatalog(mission, uid);
              return {
                ...mission,
                pointsRule: null,
                targetCount,
                completedCount,
                completedPercent,
              };
            }
            default: {
              const { targetCount, completedCount, completedPercent } =
                await this.getMissionByNormal(mission, uid);
              return {
                ...mission,
                pointsRule: null,
                targetCount,
                completedCount,
                completedPercent,
              };
            }
          }
        }
      )
    )) as any[];
  }

  async findOne(id: number): Promise<MissionEntity> {
    return await this.missionRepo.findOne({
      where: { id },
      relations: ['pointsRule'],
    });
  }

  async update(id: number, mission: CreateMissionDto): Promise<MissionEntity> {
    const { pointsRuleId, ...missionData } = mission;
    const params = {
      ...missionData,
      pointsRule: { id: pointsRuleId },
    };

    await this.missionRepo.update(id, params);

    return await this.findOne(id);
  }

  async delete(id: number): Promise<void> {
    await this.missionRepo.update(id, {
      isDeleted: CommonStatus.YES,
    });
  }

  async getMissionByUser(
    userId: number,
    tenantId?: number
  ): Promise<{ doing: CountMissionModel[]; done: CountMissionModel[] }> {
    if (isNil(userId)) {
      return { doing: [], done: [] };
    }

    const missions: CountMissionModel[] = await this.findAllByTenantId(userId);

    const doing = missions.filter(
      (mission) => mission?.completedCount > 0 && mission?.completedCount < mission.targetCount
    );
    const done = missions.filter((mission) => mission.completedCount === mission.targetCount);

    return { doing, done };
  }

  async getMissionLogByCatalogCategoryId(
    userId: number,
    categoryId: number
  ): Promise<Record<string, any>> {
    const mission: MissionEntity = await this.missionRepo.findOne({
      select: ['id', 'name', 'description', 'missionType', 'missionRelatedData'],
      where: {
        missionType: MissionType.CATALOG,
        missionRelatedData: categoryId.toString(),
        isDeleted: CommonStatus.NO as any,
      },
      relations: ['pointsRule'],
    });

    if (isNil(mission)) {
      return {
        name: '科普知识',
        targetCount: 0,
        completedCount: 0,
        completedPercent: 0,
      };
    }

    const { targetCount, completedCount, completedPercent } = await this.getMissionByCatalog(
      mission,
      userId
    );
    return {
      ...mission,
      pointsRule: null,
      targetCount,
      completedCount,
      completedPercent,
    };
  }

  /**
   * 记录目录任务
   * @description 记录用户完成目录任务的情况
   * @param userId 用户ID
   * @param dto 任务记录数据
   * @returns Promise<void>
   */
  async recordMissionCatalog(
    userId: number,
    dto: MissionRecordDto,
    mission: MissionEntity
  ): Promise<void> {
    const hasFinish = await this.missionLogService.findOne(
      userId,
      mission.id,
      mission.missionType,
      dto.relatedId
    );

    if (!isNil(hasFinish)) {
      this.logger.warn(`用户 ${userId} 已经完成了任务 ${mission.id}: ${mission.name}`);
      return;
    }

    await this.missionLogService.create(userId, mission.id, mission.missionType, dto.relatedId);

    let missionUser: MissionUserEntity = await this.missionUserRepo.findOneBy({
      mission: { id: mission.id },
      user: { id: userId },
    });

    // 如果没有这条记录，则创建
    if (isNil(missionUser)) {
      // 创建这条关联任务
      missionUser = await this.missionUserRepo.save({
        mission: { id: mission.id },
        user: { id: userId },
      });
    }

    const count = missionUser.completedCount + 1;

    await this.missionUserRepo.update(missionUser.id, {
      completedCount: count,
      isCompleted: CommonStatus.NO,
    });

    // add points
    await this.pointsService.addPointsByMission(mission.pointsRule, mission, userId, count);
  }

  /**
   * 记录问卷任务
   * @description 记录用户完成问卷任务的情况
   * @param userId 用户ID
   * @param dto 任务记录数据
   * @returns Promise<void>
   */
  async recordMissionNormal(
    userId: number,
    dto: MissionRecordDto,
    mission: MissionEntity
  ): Promise<void> {
    const hasFinish = await this.missionLogService.findOne(
      userId,
      mission.id,
      mission.missionType,
      dto.relatedId
    );

    if (!isNil(hasFinish)) {
      this.logger.warn(`用户 ${userId} 已经完成了任务 ${mission.id}: ${mission.name}`);
      return;
    }

    await this.missionLogService.create(userId, mission.id, mission.missionType, dto.relatedId);

    const missionId = mission.id;
    const targetCount = mission.pointsRule.targetCount;

    // 查 missionUserRepo 是否有这条数据
    const missionUser: MissionUserEntity = await this.missionUserRepo.findOneBy({
      mission: { id: missionId },
      user: { id: userId },
    });

    let count: number = 0;

    if (isNil(missionUser)) {
      // 创建这条关联任务
      const newMissionUser = await this.missionUserRepo.save({
        mission: { id: missionId },
        user: { id: userId },
      });

      count = newMissionUser.completedCount + 1;

      await this.missionUserRepo.update(newMissionUser.id, {
        completedCount: count,
        isCompleted: count >= targetCount ? CommonStatus.YES : CommonStatus.NO,
      });
    } else {
      if (missionUser.completedCount >= targetCount) {
        // 已经完成了
        return;
      }

      count = missionUser.completedCount + 1;

      await this.missionUserRepo.update(missionUser.id, {
        completedCount: count,
        isCompleted: count >= targetCount ? CommonStatus.YES : CommonStatus.NO,
      });
    }

    // 调用积分规则
    await this.pointsService.addPointsByMission(mission.pointsRule, mission, userId, count);
  }

  /**
   * 记录考试任务
   * @description 记录用户完成考试任务的情况
   * @param userId 用户ID
   * @param dto 任务记录数据
   * @param mission 任务实体
   * @returns Promise<void>
   */
  async recordMissionExamQuaire(
    userId: number,
    dto: MissionRecordDto,
    mission: MissionEntity
  ): Promise<void> {
    // 获取任务规则中的目标次数
    const targetCount = mission.pointsRule?.targetCount ?? 0;
    const { isPerpetual, startDate, endDate } = mission;

    // 查 missionUserRepo 是否有这条数据
    let missionUser: MissionUserEntity = await this.missionUserRepo.findOneBy({
      mission: { id: mission.id },
      user: { id: userId },
    });

    if (isNil(missionUser)) {
      // 创建这条关联任务
      missionUser = await this.missionUserRepo.save({
        mission: { id: mission.id },
        user: { id: userId },
      });
    }

    let hasFinish = missionUser.completedCount >= targetCount;

    if (hasFinish) {
      this.logger.warn(`用户 ${userId} 已经完成了任务 ${mission.id}: ${mission.name}`);
      return;
    }

    const finishCount = await this.missionLogService.findCountByExamQuiz(
      mission.missionType,
      isPerpetual,
      [startDate?.toISOString(), endDate?.toISOString()]
    );

    hasFinish = finishCount >= targetCount;

    // 如果已经完成了任务，则不再记录
    if (hasFinish) {
      this.logger.warn(`用户 ${userId} 已经完成了任务 ${mission.id}: ${mission.name}`);
      return;
    }

    // 创建任务记录
    await this.missionLogService.createByExamQuiz(userId, mission.id, mission.missionType, dto.relatedId);

    const count = missionUser.completedCount + 1;

    await this.missionUserRepo.update(missionUser.id, {
      completedCount: count,
      isCompleted: count >= targetCount ? CommonStatus.YES : CommonStatus.NO,
    });

    // 调用积分规则
    await this.pointsService.addPointsByMission(mission.pointsRule, mission, userId, count);
  }

  /**
   * 记录任务完成情况
   * @description 根据任务类型记录任务完成情况，后续可扩展更多任务类型的处理逻辑
   * @param userId 用户ID
   * @param dto 任务记录数据
   * @returns Promise<void>
   */
  async recordMissionFinish(userId: number, dto: MissionRecordDto): Promise<void> {
    if (isNil(userId)) {
      throw new BadRequestException(ErrorEnum.SYSTEM_USER_EXISTS);
    }

    // 查询所有同一类型的任务
    const missions: MissionEntity[] = await this.missionRepo.find({
      where: {
        missionType: dto.missionType,
        isDeleted: CommonStatus.NO,
      },
      relations: ['pointsRule'],
    });

    if (missions.length === 0) {
      this.logger.warn(`没有找到任务类型为 ${dto.missionType} 的任务`);
      return;
    }

    const now = dayjs();

    for (const mission of missions) {
      switch (mission.missionType) {
        case MissionType.CATALOG: {
          this.logger.log(`记录目录任务: ${dto.diseaseId} - ${mission.missionRelatedData}`);
          // 目录任务需要
          // 目录知识任务只需要计数
          if (`${dto.diseaseId}` === `${mission.missionRelatedData}`) {
            await this.recordMissionCatalog(userId, dto, mission);
          }
          break;
        }
        case MissionType.RECOMMEND: {
          if (`${dto.diseaseId}` !== `${mission.missionRelatedData}`) {
            continue;
          }

          // 判断任务是否在进行中
          if (mission.isPerpetual === CommonStatus.NO) {
            // 检查任务是否在有效期内
            if (mission.startDate && now.isBefore(dayjs(mission.startDate))) {
              this.logger.warn(
                `任务 ${mission.id}: ${mission.name} 尚未开始，开始时间: ${mission.startDate}`
              );
              continue;
            }
            if (mission.endDate && now.isAfter(dayjs(mission.endDate))) {
              this.logger.warn(
                `任务 ${mission.id}: ${mission.name} 已经结束，结束时间: ${mission.endDate}`
              );
              continue;
            }
          }

          await this.recordMissionNormal(userId, dto, mission);
          break;
        }
        case MissionType.EXAM:
        case MissionType.QNAIRE: {
          // 判断任务是否在进行中
          if (mission.isPerpetual === CommonStatus.NO) {
            // 检查任务是否在有效期内
            if (mission.startDate && now.isBefore(dayjs(mission.startDate))) {
              this.logger.warn(
                `任务 ${mission.id}: ${mission.name} 尚未开始，开始时间: ${mission.startDate}`
              );
              continue;
            }
            if (mission.endDate && now.isAfter(dayjs(mission.endDate))) {
              this.logger.warn(
                `任务 ${mission.id}: ${mission.name} 已经结束，结束时间: ${mission.endDate}`
              );
              continue;
            }
          }

          await this.recordMissionExamQuaire(userId, dto, mission);
          break;
        }
      }
    }
  }
}
